package edu.bbu.commonproject.backend.logic;

import java.util.ArrayList;
import java.util.logging.Level;

import javax.mail.MessagingException;

import edu.bbu.commonproject.backend.db.exceptions.DataBaseErrorException;
import edu.bbu.commonproject.backend.db.exceptions.DirectoryConflictException;
import edu.bbu.commonproject.backend.db.exceptions.FileDownloadException;
import edu.bbu.commonproject.backend.db.exceptions.FileNotExistException;
import edu.bbu.commonproject.backend.db.exceptions.FileUploadException;
import edu.bbu.commonproject.backend.db.exceptions.UserConflictException;
import edu.bbu.commonproject.backend.db.factory.DAOFactory;
import edu.bbu.commonproject.backend.db.model.DBDirectory;
import edu.bbu.commonproject.backend.db.model.DBFile;
import edu.bbu.commonproject.backend.db.model.DBUser;
import edu.bbu.commonproject.backend.db.services.DirectoryDAOServices;
import edu.bbu.commonproject.backend.db.services.FileDAOServices;
import edu.bbu.commonproject.backend.db.services.UserDAOServices;
import edu.bbu.commonproject.backend.logic.exceptions.DataBaseAccessException;
import edu.bbu.commonproject.backend.logic.exceptions.DecryptingPasswordException;
import edu.bbu.commonproject.backend.logic.exceptions.DirectoryOperationException;
import edu.bbu.commonproject.backend.logic.exceptions.EmailSendingException;
import edu.bbu.commonproject.backend.logic.exceptions.FileOperationException;
import edu.bbu.commonproject.backend.logic.exceptions.FileTransferException;
import edu.bbu.commonproject.backend.logic.exceptions.UserNameExistsException;
import edu.bbu.commonproject.backend.logic.exceptions.WrongPasswordException;
import edu.bbu.commonproject.backend.logic.exceptions.WrongUserNameException;
import edu.bbu.commonproject.backend.logic.utils.CryptingService;
import edu.bbu.commonproject.backend.logic.utils.MailService;
import edu.bbu.commonproject.backend.server.model.DirectoryPOJO;
import edu.bbu.commonproject.backend.server.model.FilePOJO;
import edu.bbu.commonproject.backend.server.model.UserPOJO;
import edu.bbu.commonproject.backend.utils.ServerLogger;

/**
 * Business Logic Layer, this layer should contain logic that 
 * could be used by other applications as well as this one.
 * This layer is palced between the Server and the DAO. 
 */
public class ServerBLL
{
	/** The userServices. */
	private UserDAOServices userServices;
	
	/** The fileServices. */
	private FileDAOServices fileServices;
	
	/** The directoryServices. */
	private DirectoryDAOServices directoryServices;
	
	/** The mailService. */
	private MailService mailService;
	
	/** The cryptingService. */
	private CryptingService cryptingService;
	
	/**
	 * The server business layer constructor. 
	 */
	public ServerBLL()
	{
		DAOFactory factory = DAOFactory.getDAOFactory(DAOFactory.MONGODB_FACTORY);
		
		userServices = factory.getUserDAOServices();
		fileServices = factory.getFileDAOServices();
		directoryServices = factory.getDirectoryServices();
		
		mailService = new MailService("testmongodbfs@gmail.com", "abcdefghi12345");
		cryptingService = new CryptingService();
	}
	
	/**
	 * Logging in the user.
	 * If the username does not exists, throws a WrongUserNameException.
	 * If the password dont match, throws a WrongPasswordException.
	 * @param user
	 * @throws WrongPasswordException
	 * @throws WrongUserNameException
	 * @throws DataBaseAccessException
	 */
	public void loginUser(UserPOJO user) throws WrongPasswordException, WrongUserNameException, DataBaseAccessException
	{
		DBUser dbUser = new DBUser(user.getUserName(), user.getUserPassword());
		
		try
		{
			String password = userServices.getPassword(dbUser);
			
			if(!password.equals(user.getUserPassword()))
			{
				throw new WrongPasswordException("Invalid password.");
			}
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(user.getUserName() + " not exists.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, user.getUserName() + " logged in");
	}
	
	/**
	 * Registering a new user.
	 * If the username already exists, throws a UserNameExistsException. 
	 * @param user
	 * @throws DecryptingPasswordException
	 * @throws UserNameExistsException
	 * @throws DataBaseAccessException
	 * @throws EmailSendingException
	 */
	public void registerUser(UserPOJO user) throws DecryptingPasswordException, UserNameExistsException, DataBaseAccessException, EmailSendingException 
	{
		DBUser dbUser = new DBUser(user.getUserName(), user.getUserPassword());
		String password = cryptingService.decrypt(user.getUserPassword());
		
		try
		{
			userServices.insertNewUser(dbUser);
		}
		catch (UserConflictException e)
		{
			throw new UserNameExistsException("Username already in use.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		try
		{
			directoryServices.createDirectory(directoryServices.getBaseDirectory(user.getUserName()));
		}
		catch (DirectoryConflictException | DataBaseErrorException e)
		{
			try
			{
				userServices.deleteUser(dbUser);
			}
			catch (UserConflictException | DataBaseErrorException ex){ }
			
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		try
		{
			directoryServices.setSyncDirectory(directoryServices.getDefaultSyncDirectory(user.getUserName()));
		}
		catch (DataBaseErrorException e)
		{
			try
			{
				userServices.deleteUser(dbUser);
			}
			catch (UserConflictException | DataBaseErrorException ex){ }
			
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		try
		{			
			directoryServices.createDirectory(directoryServices.getDefaultSyncDirectory(user.getUserName()));
		}
		catch (DirectoryConflictException | DataBaseErrorException e)
		{
			try
			{
				userServices.deleteUser(dbUser);
			}
			catch (UserConflictException | DataBaseErrorException ex){ }
			
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		try
		{
			mailService.sendEmail(user.getUserName(), "MongoDB file share - Confirmation", "Your account informations:\n\nUsername: " + user.getUserName() + "\nPassword: " + password);
			
		}
		catch (MessagingException e)
		{
			try
			{
				userServices.deleteUser(dbUser);
			}
			catch (UserConflictException | DataBaseErrorException ex){ }
			
			throw new EmailSendingException("Invalid e-mail adress.");
		}
		
		ServerLogger.log(Level.INFO, user.getUserName() + " registered.");
	}
	
	/**
	 * Changing the users password.
	 * If the username does not exists, throws a WrongUserNameException.
	 * If the password dont match, throws a WrongPasswordException.
	 * @param user
	 * @param newPassword
	 * @throws WrongUserNameException
	 * @throws WrongPasswordException
	 * @throws DataBaseAccessException
	 */
	public void changeUserPassword(UserPOJO user, String newPassword) throws WrongUserNameException, WrongPasswordException, DataBaseAccessException
	{
		DBUser dbUser = new DBUser(user.getUserName(), user.getUserPassword());
		
		String password;
		
		try
		{
			password = userServices.getPassword(dbUser);
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(user.getUserName() + " not exists.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		if(!password.equals(user.getUserPassword()))
		{
			throw new WrongPasswordException("Invalid password.");
		}
			
		try
		{
			userServices.editUserPassword(dbUser, newPassword);
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(user.getUserName() + " not exists.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, user.getUserName() + " changed password.");

	}
	
	/**
	 * Replacing the usesrs password if it was forgotten.
	 * If the username does not exists, throws a WrongUserNameException.
	 * @param user
	 * @param newPassword
	 * @throws DecryptingPasswordException
	 * @throws WrongUserNameException
	 * @throws DataBaseAccessException
	 * @throws EmailSendingException
	 */
	public void forgotUserPassword(UserPOJO user, String newPassword) throws DecryptingPasswordException, WrongUserNameException, DataBaseAccessException, EmailSendingException
	{
		DBUser dbUser = new DBUser(user.getUserName(), user.getUserPassword());
		String password = cryptingService.decrypt(newPassword);
		
		try
		{
			userServices.editUserPassword(dbUser, newPassword);
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(user.getUserName() + " not exists.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		try
		{
			mailService.sendEmail(user.getUserName(), "MongoDB file share - Change password", "Your account informations:\n\nUsername: " + user.getUserName() + "\nPassword: " + password);
		}
		catch (MessagingException e) 
		{
			throw new EmailSendingException("Invalid e-mail adress.");
		}
		
		ServerLogger.log(Level.INFO, user.getUserName() + " resetted the password.");
	}
	
	/**
	 * Deleting the user account.
	 * If the username does not exists, throws a WrongUserNameException.
	 * If the password dont match, throws a WrongPasswordException.
	 * @param user
	 * @throws WrongUserNameException
	 * @throws WrongPasswordException
	 * @throws DataBaseAccessException
	 */
	public void deleteUser(UserPOJO user) throws WrongUserNameException, WrongPasswordException, DataBaseAccessException
	{
		DBUser dbUser = new DBUser(user.getUserName(), user.getUserPassword());
		
		String password;
		
		try
		{
			password = userServices.getPassword(dbUser);
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(user.getUserName() + " not exists.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		if(!password.equals(user.getUserPassword()))
		{
			throw new WrongPasswordException("Invalid password.");
		}
		
		try
		{
			userServices.deleteUser(dbUser);
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(user.getUserName() + " not exists.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, user.getUserName() + " deleted the user account.");
	}
		
	/**
	 * Uploading a new file.
	 * If the connection drops down, throws a WrongUserNameException
	 * @param file
	 * @throws FileTransferException
	 * @throws DataBaseAccessException
	 */
	public void uploadFile(FilePOJO file) throws FileTransferException, DataBaseAccessException
	{
		DBFile dbFile = new DBFile();
		dbFile.setOwner(file.getOwner());
		dbFile.setFileName(file.getFileName());
		dbFile.setDirectory(file.getDirectory());
		dbFile.setFileInputStream(file.getFileInputStream());
		
		try
		{			
			fileServices.uploadFile(dbFile);
		}
		catch (FileUploadException e)
		{
			throw new FileTransferException("Uploading failed.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, "New upload: " + file.getFileName() + " from " + file.getOwner());
	}
	
	/**
	 * Downloading a file from database.
	 * If the file not found, or the connection drops, throws a FileTransferException.
	 * @param file
	 * @throws FileTransferException
	 * @throws DataBaseAccessException
	 */
	public void downloadFile(FilePOJO file) throws FileTransferException, DataBaseAccessException
	{
		DBFile dbFile = new DBFile();
		dbFile.setOwner(file.getOwner());
		dbFile.setVersion(file.getVersion());
		dbFile.setFileName(file.getFileName());
		dbFile.setDirectory(file.getDirectory());
		dbFile.setFileOutputStream(file.getFileOutputStream());
		
		try
		{			
			fileServices.downloadFile(dbFile);
		}
		catch (FileNotExistException | FileDownloadException e)
		{
			throw new FileTransferException("Downloading failed failed. The file already deleted or the connection dropped.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, "New download: " + file.getFileName() + " from " + file.getOwner());
	}
	
	/**
	 * Getting the last version number of a file. 
	 * @param file
	 * @return
	 * @throws FileOperationException
	 * @throws DataBaseAccessException
	 */
	public int getLastVersionNumber(FilePOJO file) throws FileOperationException, DataBaseAccessException
	{
		DBFile dbFile = new DBFile();
		dbFile.setOwner(file.getOwner());
		dbFile.setFileName(file.getFileName());
		dbFile.setDirectory(file.getDirectory());
		
		try
		{			
			return fileServices.getLastVersionNumber(dbFile);
		}
		catch (FileNotExistException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
	}
	
	/**
	 * Getting the version numbers from the database.
	 * @param file
	 * @return
	 * @throws FileOperationException
	 * @throws DataBaseAccessException
	 */
	public ArrayList<Integer> getFileVersionNumbers(FilePOJO file) throws FileOperationException, DataBaseAccessException
	{
		DBFile dbFile = new DBFile();
		dbFile.setOwner(file.getOwner());
		dbFile.setFileName(file.getFileName());
		dbFile.setDirectory(file.getDirectory());
			
		try
		{			
			return fileServices.getVersionNumbers(dbFile);
		}
		catch (FileNotExistException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Deleting a file from database.
	 * If the file not found, throws a FileOperationException.
	 * If an error occured on database, a DataBaseAccessException exception will be thrown.
	 * @param file
	 * @throws FileOperationException
	 * @throws DataBaseAccessException
	 */
	public void deleteFile(FilePOJO file) throws FileOperationException, DataBaseAccessException
	{
		DBFile dbFile = new DBFile();
		dbFile.setOwner(file.getOwner());
		dbFile.setVersion(file.getVersion());
		dbFile.setFileName(file.getFileName());
		dbFile.setDirectory(file.getDirectory());
		
		try
		{
			fileServices.deleteFile(dbFile);
		}
		catch (FileNotExistException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, "File delete: " + file.getFileName() + " from " + file.getOwner());
	}
	
	/**
	 * Delete all file versions. 
	 * @param file
	 * @throws FileOperationException
	 * @throws DataBaseAccessException
	 */
	public void deleteAllFileVersions(FilePOJO file) throws FileOperationException, DataBaseAccessException
	{
		DBFile dbFile = new DBFile();
		dbFile.setOwner(file.getOwner());
		dbFile.setFileName(file.getFileName());
		dbFile.setDirectory(file.getDirectory());
		
		try
		{
			fileServices.deleteAllFileVersions(dbFile);
		}
		catch (FileNotExistException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
		ServerLogger.log(Level.INFO, "File delete: " + file.getFileName() + " from " + file.getOwner());
	}
	
	/**
	 * Getting the files from directory.
	 * If somwthing wrong, FileNotExistException will be thrown.
	 * @param directoryPOJO
	 * @return
	 * @throws DataBaseAccessException
	 */
	public ArrayList<String> getFilesFromDirectory(DirectoryPOJO directoryPOJO) throws DataBaseAccessException
	{
		DBDirectory directory = new DBDirectory();
		
		directory.setOwner(directoryPOJO.getOwner());
		directory.setDirectoryName(directoryPOJO.getDirectoryName());
		directory.setParentDirectory(directoryPOJO.getParrentDirectory());
		
		try
		{
			return fileServices.getFilesFromDirectory(directory);
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the file size. 
	 * @param filePOJO
	 * @return
	 * @throws FileOperationException
	 * @throws DataBaseAccessException
	 */
	public long getFileSize(FilePOJO filePOJO) throws FileOperationException, DataBaseAccessException
	{
		DBFile file = new DBFile();
		file.setOwner(filePOJO.getOwner());
		file.setVersion(filePOJO.getVersion());
		file.setFileName(filePOJO.getFileName());
		file.setDirectory(filePOJO.getDirectory());
		
		try
		{
			return fileServices.getFileSize(file);
		}
		catch (FileNotExistException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the file upload date. 
	 * @param filePOJO
	 * @return
	 * @throws FileOperationException
	 * @throws DataBaseAccessException
	 */
	public long getFileUploadDate(FilePOJO filePOJO) throws FileOperationException, DataBaseAccessException
	{
		DBFile file = new DBFile();
		file.setOwner(filePOJO.getOwner());
		file.setVersion(filePOJO.getVersion());
		file.setFileName(filePOJO.getFileName());
		file.setDirectory(filePOJO.getDirectory());
		
		try
		{
			return fileServices.getFileUploadDate(file);
		}
		catch (FileNotExistException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Creating a directory.
	 * @param directory
	 * @throws DirectoryOperationException
	 * @throws DataBaseAccessException
	 */
	public void createDirectory(DirectoryPOJO directory) throws DirectoryOperationException, DataBaseAccessException
	{
		DBDirectory newDirectory = new DBDirectory();
		newDirectory.setOwner(directory.getOwner());
		newDirectory.setDirectoryName(directory.getDirectoryName());
		newDirectory.setParentDirectory(directory.getParrentDirectory());
		
		if(!directoryServices.isExistingDirectory(directory.getParrentDirectory(), directory.getOwner()))
		{
			DirectoryPOJO baseDirectoryPOJO = getBaseDirectory(directory.getOwner());
			
			if(!directory.equals(baseDirectoryPOJO))
			{
				throw new DirectoryOperationException("Parrent directory does not exist.");
			}
		}
		
		try
		{
			directoryServices.createDirectory(newDirectory);
		}
		catch (DirectoryConflictException e)
		{
			throw new DirectoryOperationException("Error while creating directory.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Deleting a directory. 
	 * @param directory
	 * @throws DirectoryOperationException
	 * @throws DataBaseAccessException
	 */
	public void deleteDirectory(DirectoryPOJO directory) throws DirectoryOperationException, DataBaseAccessException
	{
		DBDirectory choosedDirectory = new DBDirectory();
		choosedDirectory.setOwner(directory.getOwner());
		choosedDirectory.setDirectoryName(directory.getDirectoryName());
		choosedDirectory.setParentDirectory(directory.getParrentDirectory());
		
		try
		{
			directoryServices.deleteDirectory(choosedDirectory);
		}
		catch (DirectoryConflictException e)
		{
			throw new DirectoryOperationException("Error while deleting directory.");
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the directories from directory. 
	 * @param directory
	 * @return
	 * @throws DataBaseAccessException
	 */
	public ArrayList<String> getDirectoriesFromDirectory(DirectoryPOJO directory) throws DataBaseAccessException
	{
		DBDirectory choosedDirectory = new DBDirectory();
		choosedDirectory.setOwner(directory.getOwner());
		choosedDirectory.setDirectoryName(directory.getDirectoryName());
		choosedDirectory.setParentDirectory(directory.getParrentDirectory());
		
		try
		{
			return directoryServices.getDirectoriesFromDirectory(choosedDirectory);
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the base directory. 
	 * @param owner
	 * @return
	 */
	public DirectoryPOJO getBaseDirectory(String owner)
	{
		DBDirectory directory = directoryServices.getBaseDirectory(owner);
		
		DirectoryPOJO chooesedDirectory = new DirectoryPOJO();
		chooesedDirectory.setOwner(directory.getOwner());
		chooesedDirectory.setDirectoryName(directory.getDirectoryName());
		chooesedDirectory.setParrentDirectory(directory.getParentDirectory());
		
		return chooesedDirectory;
	}	
	
	/**
	 * Check is a directory exist. 
	 * @param directoryName
	 * @param owner
	 * @return the directory and username.
	 */
	public boolean isExistingDirectory(String directoryName, String owner)
	{
		return directoryServices.isExistingDirectory(directoryName, owner);
	}
	
	/**
	 * This method set the syncronized directory.
	 * @param directory
	 * @throws DataBaseAccessException
	 */
	public void setSyncDirectory(DirectoryPOJO directory) throws DataBaseAccessException
	{
		DBDirectory dbDirectory = new DBDirectory();
		dbDirectory.setOwner(directory.getOwner());
		dbDirectory.setDirectoryName(directory.getDirectoryName());
		dbDirectory.setParentDirectory(directory.getParrentDirectory());
		
		try
		{
			directoryServices.setSyncDirectory(dbDirectory);
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		
	}
	
	/**
	 * This method return a DirectoryPOJO.
	 * @param owner
	 * @return the owner name.
	 * @throws DataBaseAccessException
	 * @throws WrongUserNameException
	 */
	public DirectoryPOJO getSyncDirectory(String owner) throws DataBaseAccessException, WrongUserNameException
	{
		try
		{
			DBDirectory directory = directoryServices.getSyncDirectory(owner);
			
			DirectoryPOJO chooesedDirectory = new DirectoryPOJO();
			chooesedDirectory.setOwner(directory.getOwner());
			chooesedDirectory.setDirectoryName(directory.getDirectoryName());
			chooesedDirectory.setParrentDirectory(directory.getParentDirectory());
			
			return chooesedDirectory;
		}
		catch (DataBaseErrorException e)
		{
			throw new DataBaseAccessException("Error occured on database.");
		}
		catch (UserConflictException e)
		{
			throw new WrongUserNameException(owner + " not exists.");
		}
	}
}
